#!/usr/bin/env ruby

##
# Copyright (C) 2014 Hai Bison
#
# See the file LICENSE at the root directory of this project for copying
# permission.

require 'optparse'
require 'ostruct'

DESCRIPTION = "
           *** XADB v2.0.1 (r6) ***

This script forwards command to ADB (`adb`). It's useful in case you run several
emulators or real devices at the same time.

Note that you have to pass arguments in this order:

    1. Arguments for this script first.
    2. Arguments for ADB.

For example, to forward command to all devices, including emulators:

    $ #{File.basename($0)} install -r abc.apk

To forward command to all devices, but not emulators:

    $ #{File.basename($0)} -D install -r abc.apk

To forward command to all emulators, but not devices:

    $ #{File.basename($0)} -E install -r abc.apk

To forward command to a specific device:

    $ #{File.basename($0)} -d <device-id> install -r abc.apk

To forward command to a specific emulator:

    $ #{File.basename($0)} -e <emulator-id> install -r abc.apk

NOTES:

* If you don't have administrator right, you can't send command to real
  devices -- as ADB will only see their IDs as `???`. The script doesn't take
  care of this case, it lets ADB handle it.

* <device-id> is the original one returned by `adb devices`.

* <emulator-id> is just the number of port, e.g: 5554, 5556...


Anyway, this script is short, if you don't get it, you can view its source  :-)
"

##
# Parses `args` and returns
#
# * our arguments
# * ADB arguments
#
# # Parameters:
#
# * `args`: original arguments.
#
def parse_args args=ARGV.clone
    # Default options
    options = OpenStruct.new
    options.devices = nil
    options.device = nil
    options.emulators = nil
    options.emulator = nil

    # Parser
    parser = OptionParser.new do |opts|
        opts.banner = DESCRIPTION
        opts.separator ''
        opts.separator "Specific options:"

        opts.on('-D', '--devices', "forward to all devices") do |v|
            options.devices = v
        end

        opts.on('-d', '--device DEVICE_ID',
                "forward to selected device.") do |v|
            options.device = v
        end

        opts.on('-E', '--emulators', "forward to all emulators") do |v|
            options.emulators = v
        end

        opts.on('-e', '--emulator EMULATOR_ID',
                "forward to selected emulators by its port, e.g: 5554, 5556")\
                do |v|
            options.emulator = v
        end

        opts.on('-h', '--help', "Print help message.") do
            puts opts
            exit
        end
    end

    adb_args = []
    unknown_args = nil
    begin
        adb_args = parser.order!(args)
    rescue OptionParser::InvalidOption => e
        unknown_args = e.args
    end

    # We only need to append unknown args + the rest if unknwon args is
    # available. If it's nil, that means we already got all args.
    adb_args += unknown_args + args if ! unknown_args.nil?

    return options, adb_args
end # parse_args

##
# Checks to see if given name is a device's name.
#
# # Parameters:
#
# * `real_name`: real name returned from ADB.
# * `name`: name from user's command line.
#
# # Returns
#
# Positive value if given names are of a device. Otherwise, negative value
# returns.
#
def is_device real_name, name=nil
    (! real_name or real_name.empty?) ? return : nil
    name ? real_name == name : real_name.match(/^[0-9]+$/)
end # is_device

##
# Checks to see if given name is an emulator's name.
#
# # Parameters:
#
# * `name`: name returned from ADB.
# * `port`: port from user's command line.
#
# # Returns
#
# Positive value if given names are of an emulator. Otherwise, negative value
# returns.
#
def is_emulator name, port=nil
    (!name or name.empty?) ? return : nil
    port ? name == "emulator-#{port}" : name.match(/^emulator-[0-9]+$/)
end # is_emulator

if __FILE__ == $0
    ADB = 'adb'
    options, ADB_ARGS = parse_args
    #puts "options=%s ADB_ARGS=%s" % [options, ADB_ARGS]

    if ADB_ARGS.empty?
        puts ' ! No command given'
        exit(0)
    end

    ##
    # Calls ADB with given device, and passes it all ADB's arguments.
    #
    # # Parameters:
    #
    # * `device`: device or emulator name.
    #
    def call_adb device
        command = [ADB, '-s', device] + ADB_ARGS
        puts ' > %s' % command.join(' ')
        system(*command)
    end # call_adb

    devices = `#{[ADB, 'devices'].join(' ')}`.split(/[\r\n]+/)
    devices.map!{ |s| s.split(/[ \t]+/)[0] }.each do |device|
        count = 0

        if options.devices
            call_adb device if is_device device
            count += 1
        elsif options.device
            call_adb device if is_device device, options.device
            count += 1
        end

        if options.emulators
            call_adb device if is_emulator device
            count += 1
        elsif options.emulator
            call_adb device if is_emulator device, options.emulator
            count += 1
        end

        if count == 0
            call_adb device if is_device device or is_emulator device
        end
    end # for each device
end # __FILE__ == $0
